package main

import (
	"errors"
	"fmt"
)

/**
在 Go 语言中，错误是指可以预期的，并且不是非常严重，不会影响程序的运行。对于这类问题，可以用返回错误给调用者的方法，让调用者自己决定如何处理。

error接口
在 Go 语言中，错误是通过内置的 error 接口表示的。它非常简单，只有一个 Error 方法用来返回具体的错误信息，如下面的代码所示：
type error interface {
   Error() string
}
*/
//在下面的代码中，我演示了一个字符串转整数的例子：
//func main() {
//	i, err := strconv.Atoi("a")
//	if err == nil {
//		fmt.Println(i)
//	} else {
//		fmt.Println("类型转换错误：", err)
//	}
//}

/**
一般而言，error 接口用于当方法或者函数执行遇到错误时进行返回，而且是第二个返回值。
func Atoi(s string) (int, error)

error工厂函数
除了可以使用其他函数，自己定义的函数也可以返回错误信息给调用者，如下面的代码所示：
*/

func add(a, b int) (int, error) {
	if a < 0 || b < 0 {
		return 0, errors.New("a或者b不能为负数")
	} else {
		return a + b, nil
	}
}

//func main() {
//	sum,err := add(-1,2)
//	if err == nil{
//		fmt.Println(sum)
//	}else {
//		fmt.Println(err)
//	}
//}

/**
自定义error
你可能会想，上面采用工厂返回错误信息的方式只能传递一个字符串，也就是携带的信息只有字符串，如果想要携带更多信息（比如错误码信息）该怎么办呢？这个时候就需要自定义 error 。

自定义error就是自定义一个结构体
然后让其实现error接口
*/

type MyError struct {
	message string
	code    string
}

func (e *MyError) Error() string {
	fmt.Println("this is my error:", e.code, e.message)
	return fmt.Sprintf("myError:", e.code, e.message)
}

func subtract(a, b int) (int, error) {
	if a < 0 || b < 0 {
		return 0, &MyError{code: "1001", message: "a b不能为负数"}
	} else {
		return a - b, nil
	}
}

//func main() {
//	s, err := subtract(-1, 2)
//	//if err == nil {
//	//	fmt.Println(s)
//	//} else {
//	//	fmt.Println(err)
//	//}
//
//	/**
//	error 断言
//	有了自定义的 error，并且携带了更多的错误信息后，就可以使用这些信息了。你需要先把返回的 error 接口转换为自定义的错误类型，用到的知识是上节课的类型断言。
//	 */
//	if cm,ok:=err.(*MyError);ok{
//		fmt.Println("错误代码为：",cm.code,"错误信息为：",cm.message)
//	}else {
//		fmt.Println(s)
//	}
//}

// 错误嵌套 Error Wrapping
/**
在一个错误上包裹上另一个错误
这种需求是存在的，比如调用一个函数，返回了一个错误信息 error，在不想丢失这个 error 的情况下，
又想添加一些额外信息返回新的 error。这时候，我们首先想到的应该是自定义一个 struct，如下面的代码所示：
*/

// 先定一个另一个error struct 再用这个errorWrap初始化
type errorWrap struct {
	err         error
	wrapMessage string
}

/**
上面的方式可以满足我们的需求，但是非常烦琐，因为既要定义新的类型还要实现 error 接口。
所以从 Go 语言 1.13 版本开始，Go 标准库新增了 Error Wrapping 功能，
让我们可以基于一个存在的 error 生成新的 error，并且可以保留原 error 信息，如下面的代码所示
*/
//func main() {
//	err := errors.New("原始的error")
//	w := fmt.Errorf("Wrap另一个error:%s", err)
//	fmt.Println(w)
//	fmt.Println(w.Error())
//	// 既然 error 可以包裹嵌套生成一个新的 error，那么也可以被解开，即通过 errors.Unwrap 函数得到被嵌套的 error。
//	fmt.Println(errors.Unwrap(w))
//	// 有了 Error Wrapping 后，你会发现原来用的判断两个 error 是不是同一个 error 的方法失效了
//	/**
//	为什么会出现这种情况呢？由于 Go 语言的 Error Wrapping 功能，令人不知道返回的 err 是否被嵌套，又嵌套了几层？
//	于是 Go 语言为我们提供了 errors.Is 函数，用来判断两个 error 是否是同一个，如下所示：
//
//	如果 err 和 target 是同一个，那么返回 true。
//	如果 err 是一个 wrapping error，target 也包含在这个嵌套 error 链中的话，也返回 true。
//	可以简单地概括为，两个 error 相等或 err 包含 target 的情况下返回 true，其余返回 false。
//	我们可以用上面的示例判断错误 w 中是否包含错误 e，试试运行下面的代码，来看打印的结果是不是 true。
//	 */
//	fmt.Println(errors.Is(err,w))
//
//	// errors.As
//	/**
//	同样的原因，有了 error 嵌套后，error 断言也不能用了，因为你不知道一个 error 是否被嵌套，
//	又嵌套了几层。所以 Go 语言为解决这个问题提供了 errors.As 函数，比如前面 error 断言的例子，可以使用 errors.As 函数重写，效果是一样的，如下面的代码所示：
//	 */
//	var mer *MyError
//	err1 := &MyError{message: "myerror"}
//	fmt.Println(errors.As(err1,&mer))
//
//}
